package com.kakarote.core.config;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.kakarote.core.common.Const;
import com.kakarote.core.common.enums.AccessTypeEnum;
import com.kakarote.core.entity.AdminCompanySecuritySettingVO;
import com.kakarote.core.entity.UserExtraInfo;
import com.kakarote.core.entity.UserInfo;
import com.kakarote.core.exception.NoLoginException;
import com.kakarote.core.redis.Redis;
import com.kakarote.core.servlet.LoginFromCookie;
import com.kakarote.core.utils.UserUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author zhangzhiwei
 * user注入切面
 */
@Aspect
@Component
@ConditionalOnClass(value = {ServletRequest.class, HandlerMapping.class})
public class ParamAspect implements Ordered {

    /**
     * 访问类型
     */
    private static final String ACCESS_TYPE = "accessType";
    @Autowired
    private Redis redis;

    @Around("(execution(* com.kakarote.*.controller..*(..))||execution(* com.kakarote.*.*.controller..*(..))) && execution(@(org.springframework.web.bind.annotation.*Mapping) * *(..))  && !execution(@(com.kakarote.core.common.ParamAspect) * *(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        ServletRequestAttributes attributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
        HttpServletRequest request = attributes.getRequest();
        try {
            String token = request.getHeader(Const.TOKEN_NAME);

            String accessType = request.getHeader(ACCESS_TYPE);

            if (StrUtil.isEmpty(token)) {
                if (point instanceof MethodInvocationProceedingJoinPoint) {
                    Method targetMethod = ((MethodSignature) point.getSignature()).getMethod();
                    LoginFromCookie fromCookie = targetMethod.getAnnotation(LoginFromCookie.class);
                    if (fromCookie != null) {
                        for (Cookie cookie : request.getCookies()) {
                            if (Const.TOKEN_NAME.equals(cookie.getName())) {
                                token = cookie.getValue();
                                break;
                            }
                        }
                    }
                }
            }
            UserInfo info = null;
            if (StrUtil.isNotEmpty(token)) {
                String mobileLoginRedisKey = Const.USER_LOGIN_LASTED_TIME_MOBILE + token;
                //refresh mobile login cache
                if (redis.exists(mobileLoginRedisKey)) {
                    LocalDateTime tobeRefresh = redis.get(mobileLoginRedisKey);
                    if (ObjectUtil.isNotNull(tobeRefresh)) {
                        redis.expire(mobileLoginRedisKey, Const.MAX_USER_EXIST_TIME);
                    }

                }
                Object data = redis.get(token);
                if (data instanceof UserExtraInfo) {
                    throw new NoLoginException((UserExtraInfo) data);
                } else if (data instanceof UserInfo) {
                    info = (UserInfo) data;
                    //mobile login
                    if (ObjectUtil.equal(2, info.getType())) {
                        //mobile free login ckeck
                        LocalDateTime lastedLoginTime = redis.get(mobileLoginRedisKey);
                        if (ObjectUtil.isNotNull(lastedLoginTime)) {
                            LocalDateTime targetTime;
                            AdminCompanySecuritySettingVO securitySettingVO = redis.get(Const.COMPANY_SECURITY_CONFIG_KEY + ":" + 2);
                            if (ObjectUtil.isNotNull(securitySettingVO)) {
                                Integer maximumLoginFreeTime = securitySettingVO.getMaximumLoginFreeTime();
                                Integer maximumLoginFreeTimeUnit = securitySettingVO.getMaximumLoginFreeTimeUnit();
                                if (ObjectUtil.equal(maximumLoginFreeTimeUnit, 1)) {
                                    //day
                                    targetTime = lastedLoginTime.plusDays(maximumLoginFreeTime);
                                } else if (ObjectUtil.equal(maximumLoginFreeTimeUnit, 2)) {
                                    //hour
                                    targetTime = lastedLoginTime.plusHours(maximumLoginFreeTime);
                                } else {
                                    targetTime = lastedLoginTime.plusMinutes(maximumLoginFreeTime);
                                }
                                if (targetTime.isBefore(LocalDateTime.now())) {
                                    //超过最长免登录时长限制
                                    UserUtil.userLogOut(info, token);
                                    throw new NoLoginException(new UserExtraInfo(2, lastedLoginTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))));
                                }
                            }
                        }
                    }
                }
                if (info != null) {
                    AccessTypeEnum accessTypeEnum = AccessTypeEnum.parse(accessType);
                    info.setAccessType(accessTypeEnum.getType());
                    UserUtil.setUser(info);
                    //临时token无需续期
                    if (!token.startsWith(Const.TEMP_TOKEN_PREFIX)) {
                        UserUtil.userExpire(token, info);
                    }
                }
            }
            if (info == null) {
                // TODO 未登录时有个空的user对象
                info = new UserInfo();
                UserUtil.setUser(info);
            }
            return point.proceed();
        } finally {
            UserUtil.removeUser();
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}